package zdream.pmfield.stage.buff;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;

import zdream.pmfield.data.foe.Ability;
import zdream.pmfield.stage.action.BuffAction;
import zdream.pmfield.stage.message.StageMessage;
import zdream.pmfield.stage.object.FoeActor;
import zdream.pmfield.stage.object.IStageTarget;
import zdream.pmfield.stage.object.Seat;

/**
 * @author Zdream
 * @date 2023-11-25
 * @since 0.0.1
 */
public abstract class StageBuff<T extends IStageTarget> {
	public final transient T target;
	private final transient List<Consumer<T>> cleans = new ArrayList<>();

	protected StageBuff(T target) {
		this.target = Objects.requireNonNull(target);
	}

	/* ************
	 * Initiation *
	 ************ */

	public static class BuffDescription {
		public final String category;
		public FoeActor foe;
		public Seat seat;
		public Ability ability;
		public String[] properties;

		public BuffDescription(String category) {
			this.category = Objects.requireNonNull(category);
		}

		public BuffDescription withFoe(FoeActor foe) {
			this.foe = foe;
			return this;
		}

		public BuffDescription withSeat(Seat seat) {
			this.seat = seat;
			return this;
		}

		public BuffDescription withAbility(Ability ability) {
			this.ability = ability;
			return this;
		}

		public BuffDescription withProperties(String[] buffProperties) {
			this.properties = buffProperties;
			return this;
		}
	}

	/* *************
	 * Buff Action *
	 ************* */
	public Supplier<Boolean> putBuffActionNow(String task) {
		BuffAction action = new BuffAction();
		action.buff = this;
		action.task = task;
		final Supplier<Boolean> cleaner = target.getStage().timeline.putActionNow(action);
		return registerBuffActionCleaner(action, cleaner);
	}

	public Supplier<Boolean> putBuffActionWithTime(String task, float time) {
		BuffAction action = new BuffAction();
		action.buff = this;
		action.task = task;
		final Supplier<Boolean> cleaner = target.getStage().timeline.putActionWithTime(action, time);
		return registerBuffActionCleaner(action, cleaner);
	}

	private Supplier<Boolean> registerBuffActionCleaner(BuffAction action, Supplier<Boolean> cleaner) {
		final BuffActionCleaner desc = new BuffActionCleaner(action, cleaner);
		Supplier<Boolean> removeCleaner = onClean(desc);
		return () -> {
			removeCleaner.get();
			return cleaner.get();
		};
	}

	public void dispatchTask(BuffAction action) {
		this.cleans.removeIf(cleaner -> {
			if (!cleaner.getClass().equals(BuffActionCleaner.class)) {
				return false;
			}
			BuffActionCleaner cleaner1 = (BuffActionCleaner) cleaner;
			return cleaner1.action == action;
		});

		this.doTask(action);
	}

	private class BuffActionCleaner implements Consumer<T> {
		final BuffAction action;
		final Supplier<Boolean> cleaner;

		public BuffActionCleaner(BuffAction action, Supplier<Boolean> cleaner) {
			this.action = Objects.requireNonNull(action);
			this.cleaner = Objects.requireNonNull(cleaner);
		}

		@Override
		public void accept(T ignored) {
			cleaner.get();
		}
	}

	/* ************
	 * Life Cycle *
	 ************ */
	private boolean destroyed = false;

	public boolean isDestroyed() {
		return destroyed;
	}

	public void destroy() {
		if (destroyed) {
			throw new IllegalStateException(this + " 已经被删除了");
		}
		destroyed = true;

		for (Consumer<T> clean : cleans) {
			clean.accept(target);
		}
	}

	public Supplier<Boolean> onClean(Consumer<T> handler) {
		cleans.add(handler);
		return () -> cleans.remove(handler);
	}

	public abstract void init();

	public void doTask(BuffAction action) {
		throw new RuntimeException("failed to do task " + action);
	}

	/* ************
	 *  Category  *
	 ************ */

	/**
	 * 每个 Buff 有多个类别, 这里只传第一位的.
	 *
	 * 一个 {@link StageBuff} 的主 category 都不应该相同
	 */
	public abstract String getCategory();

	public boolean is(String category) {
		return getCategory().equals(category);
	}

	public boolean not(String category) {
		return !is(category);
	}

	/* ***********
	 * Delegate  *
	 *********** */

	protected float now() {
		return target.getStage().timeline.round;
	}

	protected void pushMessage(StageMessage message) {
		target.getStage().pushMessage(message);
	}
}
